1   /*
2    * Copyright (C) 2009 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.collect;
18  
19  import static com.google.common.collect.Lists.transform;
20  import static com.google.common.collect.Sets.newHashSet;
21  import static com.google.common.collect.Sets.newTreeSet;
22  import static java.lang.reflect.Modifier.isPublic;
23  import static java.lang.reflect.Modifier.isStatic;
24  
25  import com.google.common.base.Function;
26  import com.google.common.base.Joiner;
27  import com.google.common.base.Objects;
28  
29  import junit.framework.TestCase;
30  
31  import java.lang.reflect.Method;
32  import java.lang.reflect.Type;
33  import java.lang.reflect.TypeVariable;
34  import java.util.Arrays;
35  import java.util.Collections;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Set;
39  
40  /**
41   * Tests that all {@code public static} methods "inherited" from superclasses
42   * are "overridden" in each immutable-collection class. This ensures, for
43   * example, that a call written "{@code ImmutableSortedSet.copyOf()}" cannot
44   * secretly be a call to {@code ImmutableSet.copyOf()}.
45   * 
46   * @author Chris Povirk
47   */
48  public class FauxveridesTest extends TestCase {
49    public void testImmutableBiMap() {
50      doHasAllFauxveridesTest(ImmutableBiMap.class, ImmutableMap.class);
51    }
52  
53    public void testImmutableListMultimap() {
54      doHasAllFauxveridesTest(
55          ImmutableListMultimap.class, ImmutableMultimap.class);
56    }
57  
58    public void testImmutableSetMultimap() {
59      doHasAllFauxveridesTest(
60          ImmutableSetMultimap.class, ImmutableMultimap.class);
61    }
62  
63    public void testImmutableSortedMap() {
64      doHasAllFauxveridesTest(ImmutableSortedMap.class, ImmutableMap.class);
65    }
66  
67    public void testImmutableSortedSet() {
68      doHasAllFauxveridesTest(ImmutableSortedSet.class, ImmutableSet.class);
69    }
70  
71    public void testImmutableSortedMultiset() {
72      doHasAllFauxveridesTest(ImmutableSortedMultiset.class, ImmutableMultiset.class);
73    }
74  
75    /*
76     * Demonstrate that ClassCastException is possible when calling
77     * ImmutableSorted{Set,Map}.copyOf(), whose type parameters we are unable to
78     * restrict (see ImmutableSortedSetFauxverideShim).
79     */
80  
81    public void testImmutableSortedMapCopyOfMap() {
82      Map<Object, Object> original =
83          ImmutableMap.of(new Object(), new Object(), new Object(), new Object());
84  
85      try {
86        ImmutableSortedMap.copyOf(original);
87        fail();
88      } catch (ClassCastException expected) {
89      }
90    }
91  
92    public void testImmutableSortedSetCopyOfIterable() {
93      Set<Object> original = ImmutableSet.of(new Object(), new Object());
94  
95      try {
96        ImmutableSortedSet.copyOf(original);
97        fail();
98      } catch (ClassCastException expected) {
99      }
100   }
101 
102   public void testImmutableSortedSetCopyOfIterator() {
103     Set<Object> original = ImmutableSet.of(new Object(), new Object());
104 
105     try {
106       ImmutableSortedSet.copyOf(original.iterator());
107       fail();
108     } catch (ClassCastException expected) {
109     }
110   }
111 
112   private void doHasAllFauxveridesTest(Class<?> descendant, Class<?> ancestor) {
113     Set<MethodSignature> required = getAllRequiredToFauxveride(ancestor);
114     Set<MethodSignature> found = getAllFauxveridden(descendant, ancestor);
115     required.removeAll(found);
116 
117     assertEquals("Must hide public static methods from ancestor classes",
118         Collections.emptySet(), newTreeSet(required));
119   }
120 
121   private static Set<MethodSignature> getAllRequiredToFauxveride(Class<?> ancestor) {
122     return getPublicStaticMethodsBetween(ancestor, Object.class);
123   }
124 
125   private static Set<MethodSignature> getAllFauxveridden(
126       Class<?> descendant, Class<?> ancestor) {
127     return getPublicStaticMethodsBetween(descendant, ancestor);
128   }
129 
130   private static Set<MethodSignature> getPublicStaticMethodsBetween(
131       Class<?> descendant, Class<?> ancestor) {
132     Set<MethodSignature> methods = newHashSet();
133     for (Class<?> clazz : getClassesBetween(descendant, ancestor)) {
134       methods.addAll(getPublicStaticMethods(clazz));
135     }
136     return methods;
137   }
138 
139   private static Set<MethodSignature> getPublicStaticMethods(Class<?> clazz) {
140     Set<MethodSignature> publicStaticMethods = newHashSet();
141 
142     for (Method method : clazz.getDeclaredMethods()) {
143       int modifiers = method.getModifiers();
144       if (isPublic(modifiers) && isStatic(modifiers)) {
145         publicStaticMethods.add(new MethodSignature(method));
146       }
147     }
148 
149     return publicStaticMethods;
150   }
151 
152   /** [descendant, ancestor) */
153   private static Set<Class<?>> getClassesBetween(
154       Class<?> descendant, Class<?> ancestor) {
155     Set<Class<?>> classes = newHashSet();
156 
157     while (!descendant.equals(ancestor)) {
158       classes.add(descendant);
159       descendant = descendant.getSuperclass();
160     }
161 
162     return classes;
163   }
164 
165   /**
166    * Not really a signature -- just the parts that affect whether one method is
167    * a fauxveride of a method from an ancestor class.
168    * <p>
169    * See JLS 8.4.2 for the definition of the related "override-equivalent."
170    */
171   private static final class MethodSignature
172       implements Comparable<MethodSignature> {
173     final String name;
174     final List<Class<?>> parameterTypes;
175     final TypeSignature typeSignature;
176 
177     MethodSignature(Method method) {
178       name = method.getName();
179       parameterTypes = Arrays.asList(method.getParameterTypes());
180       typeSignature = new TypeSignature(method.getTypeParameters());
181     }
182 
183     @Override public boolean equals(Object obj) {
184       if (obj instanceof MethodSignature) {
185         MethodSignature other = (MethodSignature) obj;
186         return name.equals(other.name)
187             && parameterTypes.equals(other.parameterTypes)
188             && typeSignature.equals(other.typeSignature);
189       }
190 
191       return false;
192     }
193 
194     @Override public int hashCode() {
195       return Objects.hashCode(name, parameterTypes, typeSignature);
196     }
197 
198     @Override public String toString() {
199       return String.format("%s%s(%s)",
200           typeSignature, name, getTypesString(parameterTypes));
201     }
202 
203     @Override public int compareTo(MethodSignature o) {
204       return toString().compareTo(o.toString());
205     }
206   }
207 
208   private static final class TypeSignature {
209     final List<TypeParameterSignature> parameterSignatures;
210 
211     TypeSignature(TypeVariable<Method>[] parameters) {
212       parameterSignatures =
213           transform(Arrays.asList(parameters),
214               new Function<TypeVariable<?>, TypeParameterSignature>() {
215                 @Override
216                 public TypeParameterSignature apply(TypeVariable<?> from) {
217                   return new TypeParameterSignature(from);
218                 }
219               });
220     }
221 
222     @Override public boolean equals(Object obj) {
223       if (obj instanceof TypeSignature) {
224         TypeSignature other = (TypeSignature) obj;
225         return parameterSignatures.equals(other.parameterSignatures);
226       }
227 
228       return false;
229     }
230 
231     @Override public int hashCode() {
232       return parameterSignatures.hashCode();
233     }
234 
235     @Override public String toString() {
236       return (parameterSignatures.isEmpty())
237           ? ""
238           : "<" + Joiner.on(", ").join(parameterSignatures) + "> ";
239     }
240   }
241 
242   private static final class TypeParameterSignature {
243     final String name;
244     final List<Type> bounds;
245 
246     TypeParameterSignature(TypeVariable<?> typeParameter) {
247       name = typeParameter.getName();
248       bounds = Arrays.asList(typeParameter.getBounds());
249     }
250 
251     @Override public boolean equals(Object obj) {
252       if (obj instanceof TypeParameterSignature) {
253         TypeParameterSignature other = (TypeParameterSignature) obj;
254         /*
255          * The name is here only for display purposes; <E extends Number> and <T
256          * extends Number> are equivalent.
257          */
258         return bounds.equals(other.bounds);
259       }
260 
261       return false;
262     }
263 
264     @Override public int hashCode() {
265       return bounds.hashCode();
266     }
267 
268     @Override public String toString() {
269       return (bounds.equals(ImmutableList.of(Object.class)))
270           ? name
271           : name + " extends " + getTypesString(bounds);
272     }
273   }
274 
275   private static String getTypesString(List<? extends Type> types) {
276     List<String> names = transform(types, SIMPLE_NAME_GETTER);
277     return Joiner.on(", ").join(names);
278   }
279 
280   private static final Function<Type, String> SIMPLE_NAME_GETTER =
281       new Function<Type, String>() {
282         @Override
283         public String apply(Type from) {
284           if (from instanceof Class) {
285             return ((Class<?>) from).getSimpleName();
286           }
287           return from.toString();
288         }
289       };
290 }